package com.example.pt.service;

import com.example.pt.entity.TorrentClickHistory;
import com.example.pt.entity.TorrentFile;
import com.example.pt.mapper.TorrentClickHistoryMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;

import java.io.IOException;
import java.io.StringReader;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 推荐分数计算服务
 * 实现推荐算法的核心计算逻辑
 */
@Service
public class RecommendationScoreService {
    
    private static final Logger logger = LoggerFactory.getLogger(RecommendationScoreService.class);

    @Autowired
    private TorrentClickHistoryMapper torrentClickHistoryMapper;

    // 推荐公式权重参数
    private static final double CATEGORY_MATCH_WEIGHT = 0.3;    // α - 类别匹配的权重
    private static final double CONTENT_BASED_WEIGHT = 0.3;     // β - 基于内容相似度的权重
    private static final double USER_BASED_WEIGHT = 0.3;        // γ - 基于用户相似度的权重
    private static final double GLOBAL_POPULARITY_WEIGHT = 0.1; // δ - 全局热门度的权重
    
    // 时间衰减因子（单位：天）- 值越大，衰减越快
    private static final double TIME_DECAY_FACTOR = 0.05;
    
    // 最大查看历史记录数
    private static final int MAX_HISTORY_RECORDS = 50;
    
    /**
     * 计算用户对种子的推荐分数
     * @param uid 用户ID
     * @param torrent 种子对象
     * @param userClickHistory 用户点击历史
     * @param categoryClickCounts 用户各类别点击次数
     * @param totalUserClicks 用户总点击次数
     * @param maxViewCount 最高浏览量
     * @return 推荐分数（0-1之间）
     */
    public double calculateRecommendationScore(
            Integer uid, 
            TorrentFile torrent, 
            List<TorrentClickHistory> userClickHistory, 
            Map<Integer, Integer> categoryClickCounts,
            int totalUserClicks,
            int maxViewCount) {
        
        // 1. 计算类别匹配度
        double categoryMatchScore = calculateCategoryMatchScore(torrent.getCategory(), categoryClickCounts, totalUserClicks);
        
        // 2. 计算基于内容的相似度（加入时间衰减）
        double contentBasedScore = calculateContentBasedSimilarity(torrent, userClickHistory);
        
        // 3. 计算基于用户的协同过滤分数
        double userBasedScore = calculateUserBasedScore(uid, torrent.getTid());
        
        // 4. 计算全局热门度
        double globalPopularityScore = calculateGlobalPopularityScore(torrent.getViewCount(), maxViewCount);
        
        // 5. 计算最终推荐分数
        double finalScore = CATEGORY_MATCH_WEIGHT * categoryMatchScore
                          + CONTENT_BASED_WEIGHT * contentBasedScore
                          + USER_BASED_WEIGHT * userBasedScore
                          + GLOBAL_POPULARITY_WEIGHT * globalPopularityScore;
        
        // 记录详细的推荐分数计算过程
        if (logger.isDebugEnabled()) {
            logger.debug("推荐分数计算 - 种子ID: {}, 标题: {}, 类别: {}", 
                    torrent.getTid(), torrent.getTitle(), torrent.getCategory());
            logger.debug("  类别匹配度: {} (权重: {})", categoryMatchScore, CATEGORY_MATCH_WEIGHT);
            logger.debug("  内容相似度: {} (权重: {})", contentBasedScore, CONTENT_BASED_WEIGHT);
            logger.debug("  用户相似度: {} (权重: {})", userBasedScore, USER_BASED_WEIGHT);
            logger.debug("  全局热门度: {} (权重: {})", globalPopularityScore, GLOBAL_POPULARITY_WEIGHT);
            logger.debug("  最终推荐分数: {}", finalScore);
        }
        
        return finalScore;
    }
    
    /**
     * 计算类别匹配度
     * @param category 种子类别
     * @param categoryClickCounts 用户各类别点击次数
     * @param totalUserClicks 用户总点击次数
     * @return 类别匹配分数（0-1之间）
     */
    private double calculateCategoryMatchScore(int category, Map<Integer, Integer> categoryClickCounts, int totalUserClicks) {
        if (totalUserClicks == 0) {
            return 0;
        }
        
        // 用户对该类别的点击次数 / 用户总点击次数
        int categoryClicks = categoryClickCounts.getOrDefault(category, 0);
        return (double) categoryClicks / totalUserClicks;
    }
    
    /**
     * 计算基于内容的相似度
     * 通过比较种子的标题、描述、格式、语言等属性，计算内容相似度
     * 加入时间衰减因子，使最近的点击有更高权重
     * @param torrent 待推荐的种子
     * @param userClickHistory 用户点击历史
     * @return 内容相似度分数（0-1之间）
     */
    private double calculateContentBasedSimilarity(TorrentFile torrent, List<TorrentClickHistory> userClickHistory) {
        if (userClickHistory == null || userClickHistory.isEmpty()) {
            return 0;
        }
        
        // 获取用户点击过的种子列表
        List<Integer> clickedTorrentIds = userClickHistory.stream()
                .map(TorrentClickHistory::getTid)
                .distinct()
                .collect(Collectors.toList());
        
        // 如果没有点击记录，返回0
        if (clickedTorrentIds.isEmpty()) {
            return 0;
        }
        
        // 获取用户点击过的种子详情
        List<TorrentFile> clickedTorrents = torrentClickHistoryMapper.findTorrentsByIds(clickedTorrentIds);
        
        if (clickedTorrents.isEmpty()) {
            return 0;
        }
        
        // 获取当前时间
        Date currentTime = new Date();
        
        // 创建一个映射，记录每个种子ID最近的点击时间
        Map<Integer, Date> latestClickTimes = new HashMap<>();
        for (TorrentClickHistory click : userClickHistory) {
            int tid = click.getTid();
            Date clickTime = click.getClickTime();
            
            if (!latestClickTimes.containsKey(tid) || clickTime.after(latestClickTimes.get(tid))) {
                latestClickTimes.put(tid, clickTime);
            }
        }
        
        // 计算当前种子与用户点击过的每个种子的相似度，加入时间衰减
        double weightedSimilaritySum = 0;
        double weightSum = 0;
        
        for (TorrentFile clickedTorrent : clickedTorrents) {
            // 计算两个种子之间的相似度
            double similarity = calculateTorrentSimilarity(torrent, clickedTorrent);
            
            // 计算时间衰减权重
            Date latestClickTime = latestClickTimes.get(clickedTorrent.getTid());
            double timeWeight = 1.0;
            
            if (latestClickTime != null) {
                // 计算时间差（天）
                long diffInMillies = Math.abs(currentTime.getTime() - latestClickTime.getTime());
                double diffInDays = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS);
                
                // 时间衰减公式：e^(-λ * 天数)
                timeWeight = Math.exp(-TIME_DECAY_FACTOR * diffInDays);
                
                if (logger.isDebugEnabled()) {
                    logger.debug("  种子 {} 时间衰减: {} 天前点击, 权重={}", 
                            clickedTorrent.getTid(), diffInDays, timeWeight);
                }
            }
            
            // 加权计算
            weightedSimilaritySum += similarity * timeWeight;
            weightSum += timeWeight;
            
            if (logger.isDebugEnabled() && similarity > 0.5) {
                logger.debug("  种子 {} 与历史种子 {} 相似度: {}, 时间权重: {}", 
                        torrent.getTid(), clickedTorrent.getTid(), similarity, timeWeight);
            }
        }
        
        // 计算加权平均相似度
        if (weightSum > 0) {
            double weightedAvgSimilarity = weightedSimilaritySum / weightSum;
            
            if (logger.isDebugEnabled()) {
                logger.debug("  种子 {} 加权平均相似度: {}", torrent.getTid(), weightedAvgSimilarity);
            }
            
            return weightedAvgSimilarity;
        }
        
        return 0;
    }
    
    /**
     * 计算两个种子之间的相似度
     * 使用多个维度的加权计算
     * @param torrent1 种子1
     * @param torrent2 种子2
     * @return 相似度分数（0-1之间）
     */
    private double calculateTorrentSimilarity(TorrentFile torrent1, TorrentFile torrent2) {
        // 相似度权重
        final double TITLE_WEIGHT = 0.45;       // 标题权重提高到45%
        final double DESCRIPTION_WEIGHT = 0.35; // 描述权重提高到35%
        final double FORMAT_WEIGHT = 0.1;       // 格式权重保持10%
        final double LANGUAGE_WEIGHT = 0.1;     // 语言权重保持10%
        
        double similarity = 0;
        
        // 1. 标题相似度 (使用余弦相似度)
        double titleSimilarity = calculateTextSimilarity(torrent1.getTitle(), torrent2.getTitle());
        
        // 2. 描述相似度 (使用余弦相似度)
        double descriptionSimilarity = calculateTextSimilarity(
                torrent1.getDescription() != null ? torrent1.getDescription() : "",
                torrent2.getDescription() != null ? torrent2.getDescription() : "");
        
        // 3. 格式相似度 (完全匹配为1，否则为0)
        double formatSimilarity = Objects.equals(torrent1.getFormat(), torrent2.getFormat()) ? 1.0 : 0.0;
        
        // 4. 语言相似度 (完全匹配为1，否则为0)
        double languageSimilarity = Objects.equals(torrent1.getLanguage(), torrent2.getLanguage()) ? 1.0 : 0.0;
        
        // 计算加权相似度
        similarity = TITLE_WEIGHT * titleSimilarity +
                    DESCRIPTION_WEIGHT * descriptionSimilarity +
                    FORMAT_WEIGHT * formatSimilarity +
                    LANGUAGE_WEIGHT * languageSimilarity;
        
        return similarity;
    }
    
    /**
     * 计算两段文本的余弦相似度
     * 优化处理中文文本
     * @param text1 文本1
     * @param text2 文本2
     * @return 相似度分数（0-1之间）
     */
    private double calculateTextSimilarity(String text1, String text2) {
        if (text1 == null || text2 == null) {
            return 0;
        }
        
        // 判断文本是否包含中文字符
        boolean containsChinese1 = containsChineseCharacters(text1);
        boolean containsChinese2 = containsChineseCharacters(text2);
        
        // 如果两个文本都是简短的非中文文本，且完全相同，直接返回1.0
        if (!containsChinese1 && !containsChinese2 && text1.length() < 50 && text2.length() < 50 && text1.equals(text2)) {
            return 1.0;
        }
        
        // 将文本转换为词频向量 (使用IK分词)
        Map<String, Integer> vector1 = getTermFrequencyMap(text1);
        Map<String, Integer> vector2 = getTermFrequencyMap(text2);
        
        // 如果任一向量为空，返回0
        if (vector1.isEmpty() || vector2.isEmpty()) {
            return 0;
        }
        
        // 计算余弦相似度
        double dotProduct = 0.0;
        double magnitude1 = 0.0;
        double magnitude2 = 0.0;
        
        // 计算点积和向量大小
        for (String term : vector1.keySet()) {
            int freq1 = vector1.get(term);
            Integer freq2 = vector2.get(term);
            
            if (freq2 != null) {
                dotProduct += freq1 * freq2;
            }
            
            magnitude1 += Math.pow(freq1, 2);
        }
        
        for (int freq2 : vector2.values()) {
            magnitude2 += Math.pow(freq2, 2);
        }
        
        // 计算余弦相似度
        if (magnitude1 > 0 && magnitude2 > 0) {
            double similarity = dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
            
            // 记录高相似度的文本对，用于调试
            if (similarity > 0.8 && logger.isDebugEnabled()) {
                logger.debug("高相似度文本对 (相似度={}): \n文本1: {}\n文本2: {}", 
                        similarity, truncateText(text1, 100), truncateText(text2, 100));
            }
            
            return similarity;
        } else {
            return 0;
        }
    }
    
    /**
     * 检查文本是否包含中文字符
     * @param text 待检查文本
     * @return 是否包含中文
     */
    private boolean containsChineseCharacters(String text) {
        if (text == null || text.isEmpty()) {
            return false;
        }
        
        for (char c : text.toCharArray()) {
            if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 截断文本，超过最大长度时添加省略号
     * @param text 原文本
     * @param maxLength 最大长度
     * @return 截断后的文本
     */
    private String truncateText(String text, int maxLength) {
        if (text == null || text.length() <= maxLength) {
            return text;
        }
        
        return text.substring(0, maxLength) + "...";
    }
    
    /**
     * 将文本转换为词频向量
     * 使用IK分词器处理中文文本
     * @param text 输入文本
     * @return 词频映射
     */
    private Map<String, Integer> getTermFrequencyMap(String text) {
        Map<String, Integer> termFrequencyMap = new HashMap<>();
        
        if (text == null || text.isEmpty()) {
            return termFrequencyMap;
        }
        
        try {
            // 使用IK分词器处理文本
            IKSegmenter ikSegmenter = com.example.pt.config.IKAnalyzerConfig.createIKSegmenter(text, true); // true表示使用智能分词模式
            Lexeme lexeme;
            
            // 遍历分词结果
            while ((lexeme = ikSegmenter.next()) != null) {
                String term = lexeme.getLexemeText().toLowerCase(); // 转小写
                if (!term.isEmpty()) {
                    termFrequencyMap.put(term, termFrequencyMap.getOrDefault(term, 0) + 1);
                }
            }
        } catch (IOException e) {
            logger.error("IK分词器处理文本失败: {}", e.getMessage(), e);
            
            // 分词失败时回退到简单分词
            fallbackSimpleTokenize(text, termFrequencyMap);
        }
        
        // 如果IK分词没有产生任何结果（可能是非中文文本），使用简单分词作为回退
        if (termFrequencyMap.isEmpty() && !text.isEmpty()) {
            fallbackSimpleTokenize(text, termFrequencyMap);
        }
        
        return termFrequencyMap;
    }
    
    /**
     * 简单分词方法（作为回退处理）
     * @param text 输入文本
     * @param termFrequencyMap 词频映射
     */
    private void fallbackSimpleTokenize(String text, Map<String, Integer> termFrequencyMap) {
        // 简单分词（按空格和标点符号分割）
        String[] terms = text.toLowerCase()
                .replaceAll("[\\p{Punct}]", " ")
                .split("\\s+");
        
        // 统计词频
        for (String term : terms) {
            if (!term.isEmpty()) {
                termFrequencyMap.put(term, termFrequencyMap.getOrDefault(term, 0) + 1);
            }
        }
        
        logger.debug("使用简单分词回退处理文本: {}", text);
    }
    
    /**
     * 计算基于用户的协同过滤分数
     * 找到与当前用户相似的用户，然后推荐他们喜欢但当前用户未接触过的种子
     * 加入时间衰减因子，使最近的行为有更高权重
     * @param uid 用户ID
     * @param tid 种子ID
     * @return 基于用户的推荐分数（0-1之间）
     */
    private double calculateUserBasedScore(Integer uid, Integer tid) {
        try {
            // 1. 获取与当前用户相似的用户列表
            List<Map<String, Object>> similarUsers = torrentClickHistoryMapper.findSimilarUsers(uid);
            
            if (similarUsers.isEmpty()) {
                return 0;
            }
            
            // 2. 计算相似用户对该种子的兴趣度（加入时间衰减）
            double totalInterest = 0;
            double totalSimilarity = 0;
            
            Date currentTime = new Date();
            
            for (Map<String, Object> similarUser : similarUsers) {
                int similarUid = ((Number) similarUser.get("uid")).intValue();
                double similarity = ((Number) similarUser.get("similarity")).doubleValue();
                
                // 获取该用户对该种子的点击历史（包含时间）
                List<Date> clickTimes = torrentClickHistoryMapper.getUserTorrentClickTimes(similarUid, tid);
                
                if (!clickTimes.isEmpty()) {
                    // 计算时间衰减的兴趣度
                    double userInterest = 0;
                    
                    for (Date clickTime : clickTimes) {
                        // 计算时间差（天）
                        long diffInMillies = Math.abs(currentTime.getTime() - clickTime.getTime());
                        double diffInDays = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS);
                        
                        // 时间衰减公式：e^(-λ * 天数)
                        double timeWeight = Math.exp(-TIME_DECAY_FACTOR * diffInDays);
                        userInterest += timeWeight;
                    }
                    
                    // 加权计算兴趣度
                    totalInterest += similarity * userInterest;
                    totalSimilarity += similarity;
                }
            }
            
            // 3. 归一化处理，确保分数在0-1之间
            if (totalSimilarity > 0) {
                double normalizedScore = totalInterest / (totalSimilarity * 3); // 假设最多点击3次为满分
                return Math.min(1.0, normalizedScore);
            } else {
                return 0;
            }
        } catch (Exception e) {
            logger.error("计算基于用户的推荐分数时发生错误: {}", e.getMessage(), e);
            return 0;
        }
    }
    
    /**
     * 计算全局热门度
     * @param viewCount 种子浏览量
     * @param maxViewCount 最高浏览量
     * @return 全局热门度分数（0-1之间）
     */
    private double calculateGlobalPopularityScore(int viewCount, int maxViewCount) {
        if (maxViewCount == 0) {
            return 0;
        }
        
        // 种子浏览量 / 最高浏览量
        return (double) viewCount / maxViewCount;
    }
    
    /**
     * 获取用户点击历史及统计数据
     * @param uid 用户ID
     * @return 包含点击历史和统计信息的Map
     */
    public Map<String, Object> getUserClickHistoryStats(Integer uid) {
        Map<String, Object> result = new HashMap<>();
        
        // 获取用户最近点击历史
        List<TorrentClickHistory> userHistory = torrentClickHistoryMapper.findRecentClicksByUser(uid, MAX_HISTORY_RECORDS);
        
        if (userHistory == null || userHistory.isEmpty()) {
            logger.info("用户 {} 没有点击历史记录", uid);
            result.put("userClickHistory", new ArrayList<>());
            result.put("categoryClickCounts", new HashMap<>());
            result.put("totalUserClicks", 0);
            result.put("clickedTids", new ArrayList<>());
            return result;
        }
        
        // 获取用户点击的种子ID列表
        List<Integer> clickedTids = userHistory.stream()
                .map(TorrentClickHistory::getTid)
                .collect(Collectors.toList());
        
        // 获取各类别点击统计
        Map<Integer, Integer> categoryClickCounts = new HashMap<>();
        List<Map<String, Object>> categoryStats = torrentClickHistoryMapper.getUserCategoryClickStats(uid);
        
        for (Map<String, Object> stat : categoryStats) {
            int category = ((Number) stat.get("category")).intValue();
            int clickCount = ((Number) stat.get("click_count")).intValue();
            categoryClickCounts.put(category, clickCount);
        }
        
        // 获取用户总点击数
        int totalClicks = torrentClickHistoryMapper.getUserTotalClicks(uid);
        
        logger.info("用户 {} 点击历史统计: 总点击数: {}, 点击类别数: {}", 
                uid, totalClicks, categoryClickCounts.size());
        if (logger.isDebugEnabled()) {
            logger.debug("用户 {} 类别点击分布: {}", uid, categoryClickCounts);
        }
        
        // 将结果保存到返回Map中
        result.put("userClickHistory", userHistory);
        result.put("categoryClickCounts", categoryClickCounts);
        result.put("totalUserClicks", totalClicks);
        result.put("clickedTids", clickedTids);
        
        return result;
    }
    
    /**
     * 获取系统中最高浏览量
     * @return 最高浏览量
     */
    public int getMaxViewCount() {
        return torrentClickHistoryMapper.getMaxViewCount();
    }
} 